package com.jiangyg.mall.order.service;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jiangyg.mall.cart.service.CartService;
import com.jiangyg.mall.cart.vo.CartItemVO;
import com.jiangyg.mall.core.constant.CoreConstant;
import com.jiangyg.mall.core.exception.BusinessException;
import com.jiangyg.mall.core.support.snowflake.SnowflakeBuilder;
import com.jiangyg.mall.core.utils.ConfuseUtils;
import com.jiangyg.mall.core.utils.Logger;
import com.jiangyg.mall.core.utils.UUIDUtils;
import com.jiangyg.mall.db.mybatis.BaseServiceImpl;
import com.jiangyg.mall.member.service.MemberReceiveAddressService;
import com.jiangyg.mall.member.vo.MemberReceiveAddressVO;
import com.jiangyg.mall.order.constant.OrderConstant;
import com.jiangyg.mall.order.dao.OrderDao;
import com.jiangyg.mall.order.dto.OrderReleaseDTO;
import com.jiangyg.mall.order.dto.OrderSubmitDTO;
import com.jiangyg.mall.order.dto.SeckillOrderDTO;
import com.jiangyg.mall.order.entity.Order;
import com.jiangyg.mall.order.entity.OrderItem;
import com.jiangyg.mall.order.enums.ConfirmStatus;
import com.jiangyg.mall.order.enums.DeleteStatus;
import com.jiangyg.mall.order.enums.OrderReleaseType;
import com.jiangyg.mall.order.enums.OrderStatus;
import com.jiangyg.mall.order.vo.OrderConfirmVO;
import com.jiangyg.mall.product.service.SpuService;
import com.jiangyg.mall.product.vo.SpuInfoVO;
import com.jiangyg.mall.ware.dto.WareLockStockDTO;
import com.jiangyg.mall.ware.service.WareSkuService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.annotation.Service;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class OrderServiceImpl extends BaseServiceImpl<OrderDao, Order> implements OrderService {

    private final TaskExecutor executor;

    private final RabbitTemplate rabbitTemplate;

    private final SqlSessionTemplate sessionTemplate;

    private final RedisTemplate<String, Object> redisTemplate;

//    @Reference
    @Autowired
    private SpuService spuService;

//    @Reference
    @Autowired
    private CartService cartService;

//    @Reference
    @Autowired
    private WareSkuService wareSkuService;

//    @Reference
    @Autowired
    private MemberReceiveAddressService memberReceiveAddressService;

    /**
     * 验证和删除防重令牌的 lua 脚本
     */
    private static final String TOKEN_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    public OrderServiceImpl(OrderDao orderDao,
                            RabbitTemplate rabbitTemplate,
                            SqlSessionTemplate sqlSessionTemplate,
                            RedisTemplate<String, Object> redisTemplate,
                            @Qualifier(CoreConstant.ASYNC_EXECUTOR) TaskExecutor executor) {
        super(orderDao);
        this.executor = executor;
        this.redisTemplate = redisTemplate;
        this.rabbitTemplate = rabbitTemplate;
        this.sessionTemplate = sqlSessionTemplate;
    }

    @Override
    public OrderConfirmVO info(Long memberId) throws Exception {
        final OrderConfirmVO result = new OrderConfirmVO();

        // 1. 异步查询会员所有的收货地址（对联系电话做混淆处理）
        final CompletableFuture<Void> receiveAddressFuture = CompletableFuture
                .runAsync(() -> selectReceiveAddressList(memberId, result), executor);

        // 2. 异步查询购物车选中商品信息、商品的库存信息
        // noinspection NonAsciiCharacters
        final CompletableFuture<Object> cartFuture = CompletableFuture
                // 2.1 异步查询购物车商品明细
                .supplyAsync(() -> selectCartItemList(memberId, result), executor)
                // 2.2 计算订单总额
                .thenApply(skuList -> computedOrderAmount(skuList, result));

        // TODO 3. 查询会员积分信息

        // TODO 优惠券信息

        // 4. 生成防重令牌（防止订单重复提交）
        final String token = UUIDUtils.build();
        result.setToken(token);
        final String cacheKey = OrderConstant.tokenCacheKey(memberId);
        redisTemplate.opsForValue().set(cacheKey, token, 30, TimeUnit.MINUTES);

        // 5. 阻塞等待所有异步任务完成
        CompletableFuture.allOf(receiveAddressFuture, cartFuture).get();

        return result;
    }

    /**
     * 功能描述：查询收货地址列表
     *
     * @param memberId 会员ID
     * @param result   订单确认页返回数据
     */
    private void selectReceiveAddressList(Long memberId, OrderConfirmVO result) {
        try {
            final List<MemberReceiveAddressVO> receiveAddressList = memberReceiveAddressService.selectListByMemberId(memberId);
            receiveAddressList.forEach(ra -> ra.setPhone(ConfuseUtils.phone(ra.getPhone())));
            result.setAddressList(receiveAddressList);
        } catch (Exception ex) {
            Logger.error(log, () -> "[订单确认页面]-获取会员的收货地址列表失败", ex);
        }
    }

    /**
     * 功能描述：查询购物车商品列表并设置返回数据
     *
     * @param memberId 会员ID
     * @param result   订单确认页返回数据
     * @return 购物车商品列表
     */
    private List<CartItemVO> selectCartItemList(Long memberId, OrderConfirmVO result) {
        try {
            final List<CartItemVO> itemList = cartService.selectItemList(memberId, true);
            result.setSkuList(itemList);
            return itemList;
        } catch (Exception ex) {
            Logger.error(log, () -> "[订单确认页面]-获取会员的商品列表失败", ex);
            return Collections.emptyList();
        }
    }

    /**
     * 功能描述：计算订单总额
     *
     * @param skuList 商品明细
     * @param result  订单返回VO
     * @return 商品明细
     */
    private List<CartItemVO> computedOrderAmount(List<CartItemVO> skuList, OrderConfirmVO result) {
        final double amount = skuList
                .stream()
                .filter(CartItemVO::isChecked)
                .map(CartItemVO::getAmount)
                .map(BigDecimal::new)
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO)
                .doubleValue();
        result.setAmount(amount);
        return skuList;
    }

    @Override
    public void submit(OrderSubmitDTO dto, Long memberId, String memberUsername) throws Exception {

        // 1. 验证订单令牌是否有效
        this.verifyToken(dto, memberId);

        // 2. 购物车选中商品明细（购物车明细不能为空）
        final List<CartItemVO> skuList = cartService.selectItemList(memberId, true);
        if (CollectionUtils.isEmpty(skuList)) {
            throw BusinessException.instance("mall.order.submit.sku.none.error");
        }

        // 3. 生成订单主表信息
        final Order order = this.createOrder(dto, memberId, memberUsername);

        // 4. 生成订单明细表信息
        final List<OrderItem> itemList = this.createOrderItemList(skuList, order);

        // 5. 计算订单相关数值
        this.computedOrderNumeric(order, itemList);

        // 6. 验证价格
        this.verifyOrderAmount(dto, order);

        // 5. 创建订单信息
        // 第一步先保存订单信息、第二步进行库存锁定、第三步发送消息到延期队列
        // 任何一步发生异常都会进行事物回滚
        this.batchSaveOrder(order, itemList, () -> {
            // 5.1 库存锁定
            boolean success = this.lockStock(order, itemList);
            if (!success) {
                throw BusinessException.instance("mall.order.submit.no.stock.error");
            }
            // 5.2 发送订单信息到延期队列中，用于实现订单到期自动取消的功能
            this.sendOrderMessage(order);
        });

        // 6. 订单提交成功之后，删除购物车选中数据
        deleteCartSubmittedSku(skuList, memberId);
    }

    /**
     * 功能描述：校验订单令牌
     *
     * @param dto      提交参数
     * @param memberId 会员ID
     */
    private void verifyToken(OrderSubmitDTO dto, long memberId) {
        final String cacheKey = OrderConstant.tokenCacheKey(memberId);
        Long status = redisTemplate.execute(new DefaultRedisScript<>(TOKEN_SCRIPT, Long.class),
                Collections.singletonList(cacheKey), dto.getToken());
        if (status == null || status == 0) {
            throw BusinessException.instance("mall.order.submit.token.invalid.error");
        }
    }

    /**
     * 功能描述：校验订单价格
     *
     * @param dto   提交参数
     * @param order 订单主表
     */
    private void verifyOrderAmount(OrderSubmitDTO dto, Order order) {
        BigDecimal payAmount = order.getPayAmount();
        BigDecimal payPrice = BigDecimal.valueOf(dto.getPayPrice());
        if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
            throw BusinessException.instance("mall.order.submit.pirce.change.error");
        }
    }

    /**
     * 功能描述：创建订单主表对象
     *
     * @param dto            订单提交参数
     * @param memberId       会员ID
     * @param memberUsername 登录会员名称
     * @return 订单主表对象
     */
    private Order createOrder(OrderSubmitDTO dto, long memberId, String memberUsername) throws Exception {
        // 1. 生成订单号
        final Long orderId = SnowflakeBuilder.nextId();

        // 2. 创建订单主表对象
        final Order order = new Order();
        order.setId(orderId);
        order.setMemberId(memberId);
        order.setMemberUsername(memberUsername);

        // 3. 设置收货信息（收货信息用户可以随时修改，所以需要保存当前详细信息）
        final Long receiveAddressId = dto.getReceiveAddressId();
        final MemberReceiveAddressVO receiveAddress = memberReceiveAddressService.selectById(receiveAddressId, memberId);
        order.setReceiverName(receiveAddress.getName());
        order.setReceiverPhone(receiveAddress.getPhone());
        order.setReceiverPostCode(receiveAddress.getPostcode());
        order.setReceiverProvince(receiveAddress.getProvince());
        order.setReceiverCity(receiveAddress.getCity());
        order.setReceiverRegion(receiveAddress.getRegion());
        order.setReceiverDetailAddress(receiveAddress.getDetailAddress());

        // 4. TODO 运费信息（应该做成针对特殊地区可手动设置运费、其他地区自动设置运费，这样的表）
        order.setFreightAmount(BigDecimal.ZERO);

        // 5. 其他信息初始化
        order.setPayType(dto.getPayType());
        order.setRemark(dto.getRemark());

        // 6. 订单状态初始化
        // TODO 自动确认收货日期可配置
        order.setStatus(OrderStatus.NEW.code);
        order.setAutoConfirmDay(7);
        order.setConfirmStatus(ConfirmStatus.UNCONFIRMED.code);
        order.setModifyTime(new Date());
        order.setCreateTime(new Date());

        return order;
    }

    /**
     * 功能描述：根据购物车明细生成订单明细
     *
     * @param skuList 购物车明细
     * @param order   订单主表
     * @return 订单明细
     */
    private List<OrderItem> createOrderItemList(List<CartItemVO> skuList, Order order) throws Exception {
        // 1. 批量查询 SPU 信息
        final List<SpuInfoVO> spuInfos;
        {
            final List<Long> spuIds = skuList.stream()
                    .map(CartItemVO::getSpuId)
                    .map(Long::parseLong)
                    .distinct()
                    .collect(Collectors.toList());
            spuInfos = this.spuService.selectListByIds(spuIds);
        }
        // 2. 根据购物车明细生成订单明细
        final List<OrderItem> itemList = new ArrayList<>(skuList.size());
        for (CartItemVO sku : skuList) {
            final OrderItem item = new OrderItem();
            item.setOrderId(order.getId());

            // 设置 spu 信息
            final SpuInfoVO spuInfo = spuInfos.stream()
                    .filter(s -> s.getId().equals(sku.getSpuId()))
                    .findFirst()
                    .orElseThrow(() -> BusinessException.instance("mall.order.submit.sku.mistake.error"));
            item.setSpuId(Long.parseLong(spuInfo.getId()));
            item.setSpuName(spuInfo.getName());
            item.setBrandId(Long.parseLong(spuInfo.getBrandId()));
            item.setCategoryId(Long.parseLong(spuInfo.getCategoryId()));

            // 设置 sku 信息
            item.setSkuId(Long.parseLong(sku.getId()));
            item.setSkuName(sku.getTitle());
            item.setSkuImage(sku.getImage());
            item.setSkuPrice(BigDecimal.valueOf(sku.getPrice()));
            item.setSkuQuantity(sku.getCount());
            item.setSkuAttrsVals(StringUtils.collectionToDelimitedString(sku.getAttrs(), ";"));

            // TODO 商品的积分信息
            item.setGiftGrowth(BigDecimal.valueOf(sku.getPrice()).multiply(new BigDecimal(sku.getCount())).intValue());
            item.setGiftIntegration(BigDecimal.valueOf(sku.getPrice()).multiply(new BigDecimal(sku.getCount())).intValue());

            // TODO 商品的优惠信息
            // TODO 订单项的价格信息
            item.setPromotionAmount(BigDecimal.ZERO);
            item.setCouponAmount(BigDecimal.ZERO);
            item.setIntegrationAmount(BigDecimal.ZERO);

            // TODO 当前订单项的实际金额.总额 - 各种优惠价格
            // 原来的价格
            BigDecimal origin = item.getSkuPrice().multiply(new BigDecimal(item.getSkuQuantity().toString()));
            // 原价减去优惠价得到最终的价格
            BigDecimal subtract = origin.subtract(item.getCouponAmount())
                    .subtract(item.getPromotionAmount())
                    .subtract(item.getIntegrationAmount());
            item.setRealAmount(subtract);

            itemList.add(item);
        }
        return itemList;
    }

    /**
     * 功能描述：计算订单相关数值
     *
     * @param order    订单主表
     * @param itemList 订单明细
     */
    private void computedOrderNumeric(Order order, List<OrderItem> itemList) {
        // 定义总价
        BigDecimal totalAmount = new BigDecimal("0.0");
        // 定义优惠价
        BigDecimal couponAmount = new BigDecimal("0.0");
        BigDecimal intergrationAmount = new BigDecimal("0.0");
        BigDecimal promotionAmount = new BigDecimal("0.0");
        // 定义积分、成长值
        Integer giftIntegrationTotal = 0;
        Integer giftGrowth = 0;

        // 订单总额，叠加每一个订单项的总额信息
        for (OrderItem item : itemList) {
            // 总价
            totalAmount = totalAmount.add(item.getRealAmount());
            // 优惠价格信息
            couponAmount = couponAmount.add(item.getCouponAmount());
            promotionAmount = promotionAmount.add(item.getPromotionAmount());
            intergrationAmount = intergrationAmount.add(item.getIntegrationAmount());
            // 积分信息和成长值信息
            giftIntegrationTotal += item.getGiftIntegration();
            giftGrowth += item.getGiftGrowth();
        }

        // 订单价格相关的
        order.setTotalAmount(totalAmount);
        // 设置应付总额(总额+运费)
        order.setPayAmount(totalAmount.add(order.getFreightAmount()));
        order.setCouponAmount(couponAmount);
        order.setPromotionAmount(promotionAmount);
        order.setIntegrationAmount(intergrationAmount);
        // 设置积分成长值信息
        order.setIntegration(giftIntegrationTotal);
        order.setGrowth(giftGrowth);

        // 设置删除状态（0-未删除，1-已删除）
        order.setDeleteStatus(DeleteStatus.UNDELETED.code);
    }

    /**
     * 功能描述：提交订单信息
     *
     * @param order    订单主表
     * @param itemList 订单明细
     */
    private void batchSaveOrder(Order order, List<OrderItem> itemList, Callback callback) {
        final SqlSessionFactory sessionFactory = sessionTemplate.getSqlSessionFactory();
        SqlSession session = null;
        try {
            session = sessionFactory.openSession(ExecutorType.BATCH, false);
            // 提交订单主表信息
            {
                final String statement = SqlHelper.table(Order.class).getSqlStatement(SqlMethod.INSERT_ONE.getMethod());
                session.insert(statement, order);
            }
            // 提交订单明细信息
            {
                final String statement = SqlHelper.table(OrderItem.class).getSqlStatement(SqlMethod.INSERT_ONE.getMethod());
                for (OrderItem item : itemList) {
                    session.insert(statement, item);
                }
            }
            final List<BatchResult> batchResults = session.flushStatements();
            // TODO 判断提交是否成功
            System.out.println(batchResults);
            // 提交之后的回调函数
            if (callback != null) {
                callback.accept();
            }
            session.commit();
        } catch (Exception ex) {
            Logger.error(log, () -> "[提交订单]-批量提交订单数据失败", ex);
            if (session != null) {
                session.rollback();
            }
        } finally {
            IOUtils.closeQuietly(session);
        }
    }

    /**
     * 功能描述：删除购物车已提交订单的商品
     *
     * @param skuList  订单商品列表
     * @param memberId 会员ID
     */
    private void deleteCartSubmittedSku(List<CartItemVO> skuList, long memberId) {
        final List<Long> skuIds = skuList.stream().map(CartItemVO::getId).map(Long::parseLong).collect(Collectors.toList());
        this.cartService.deleteItem(memberId, skuIds);
    }

    /**
     * 功能描述：锁定库存
     *
     * @param order    订单
     * @param itemList 订单明细
     * @return 是否锁定成功
     */
    private boolean lockStock(Order order, List<OrderItem> itemList) {
        // 锁定库存
        final WareLockStockDTO dto = new WareLockStockDTO();
        dto.setOrderId(order.getId());
        dto.setReceiverName(order.getReceiverName());
        dto.setReceiverPhone(order.getReceiverPhone());
        dto.setReceiverDetailAddress(order.getReceiverDetailAddress());
        dto.setRemark(order.getRemark());
        // 锁定库存明细
        final List<WareLockStockDTO.Detail> details = itemList.stream()
                .map(item -> new WareLockStockDTO.Detail(item.getSkuId(), item.getSkuName(), item.getSkuQuantity()))
                .collect(Collectors.toList());
        dto.setDetails(details);
        return this.wareSkuService.lockStock(dto);
    }

    /**
     * 功能描述：发送订单消息
     *
     * @param order 订单信息
     */
    private void sendOrderMessage(Order order) {
        final OrderReleaseDTO orderReleaseDTO = new OrderReleaseDTO();
        orderReleaseDTO.setId(order.getId());
        orderReleaseDTO.setType(OrderReleaseType.DELAY.code);
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.main", orderReleaseDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void release(OrderReleaseDTO dto, Long memberId) {
        // 1. 参数校验
        if (OrderReleaseType.decide(dto.getType()) == OrderReleaseType.MANUAL && memberId == null) {
            throw BusinessException.instance("mall.order.release.params.error");
        }

        // 2. 查询订单信息，如果订单不存在则直接退出
        final Order order = this.baseMapper.selectById(dto.getId());
        if (order == null) {
            Logger.info(log, () -> String.format("[订单释放]-订单[%d]不存在", dto.getId()));
            return;
        }

        // TODO 不能取消非本人的订单，但是后台可以操作吗？
        if (OrderReleaseType.decide(dto.getType()) == OrderReleaseType.MANUAL
                && !memberId.equals(order.getMemberId())) {
            throw BusinessException.instance("mall.order.release.params.error");
        }

        // 3. 如果订单非待付款状态则直接退出（说明订单已付款）
        if (OrderStatus.decide(order.getStatus()) != OrderStatus.NEW) {
            return;
        }

        // 4. 待付款订单进行关闭处理
        final Order orderUpdate = new Order();
        orderUpdate.setId(order.getId());
        orderUpdate.setStatus(OrderStatus.CANCELLED.code);
        this.baseMapper.updateById(orderUpdate);

        // 5. 发送订单取消消息
        // TODO 确保每个消息发送成功，给每个消息做好日志记录，(给数据库保存每一个详细信息)保存每个消息的详细信息
        try {
            rabbitTemplate.convertAndSend("order-event-exchange", "order.release.branch", dto);
        } catch (Exception ex) {
            // TODO 定期扫描数据库，重新发送失败的消息
        }
    }

    @Override
    public OrderStatus selectOrderStatus(Long orderId) {
        final Order order = this.baseMapper.selectById(orderId);
        if (order == null) {
            return null;
        }
        return OrderStatus.decide(order.getStatus());
    }

    @Override
    public void seckill(SeckillOrderDTO dto) {
        // TODO 秒杀订单创建处理
    }

    /**
     * 接口描述：可抛出异常的回调函数
     */
    private interface Callback {

        void accept() throws Exception;

    }

}
